diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts
index 63f0319442..a1a257fbd1 100644
--- a/packages/backend/src/core/CustomEmojiService.ts
+++ b/packages/backend/src/core/CustomEmojiService.ts
@@ -61,7 +61,7 @@ export class CustomEmojiService {
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 
 			this.globalEventService.publishBroadcastStream('emojiAdded', {
-				emoji: await this.emojiEntityService.pack(emoji.id),
+				emoji: await this.emojiEntityService.packDetailed(emoji.id),
 			});
 		}
 
diff --git a/packages/backend/src/core/entities/EmojiEntityService.ts b/packages/backend/src/core/entities/EmojiEntityService.ts
index f4a01ab032..f5c8f2d4bb 100644
--- a/packages/backend/src/core/entities/EmojiEntityService.ts
+++ b/packages/backend/src/core/entities/EmojiEntityService.ts
@@ -5,44 +5,59 @@ import type { Packed } from '@/misc/schema.js';
 import type { } from '@/models/entities/Blocking.js';
 import type { Emoji } from '@/models/entities/Emoji.js';
 import { bindThis } from '@/decorators.js';
-import { UserEntityService } from './UserEntityService.js';
 
 @Injectable()
 export class EmojiEntityService {
 	constructor(
 		@Inject(DI.emojisRepository)
 		private emojisRepository: EmojisRepository,
-
-		private userEntityService: UserEntityService,
 	) {
 	}
 
 	@bindThis
-	public async pack(
+	public async packSimple(
 		src: Emoji['id'] | Emoji,
-		opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = { omitHost: true, omitId: true, withUrl: true },
-	): Promise<Packed<'Emoji'>> {
-		opts = { omitHost: true, omitId: true, withUrl: true, ...opts };
-
+	): Promise<Packed<'EmojiSimple'>> {
 		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
 
 		return {
-			id: opts.omitId ? undefined : emoji.id,
 			aliases: emoji.aliases,
 			name: emoji.name,
 			category: emoji.category,
-			host: opts.omitHost ? undefined : emoji.host,
 			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
-			url: opts.withUrl ? (emoji.publicUrl || emoji.originalUrl) : undefined,
+			url: emoji.publicUrl || emoji.originalUrl,
 		};
 	}
 
 	@bindThis
-	public packMany(
+	public packSimpleMany(
 		emojis: any[],
-		opts: { omitHost?: boolean; omitId?: boolean; withUrl?: boolean; } = {},
 	) {
-		return Promise.all(emojis.map(x => this.pack(x, opts)));
+		return Promise.all(emojis.map(x => this.packSimple(x)));
+	}
+
+	@bindThis
+	public async packDetailed(
+		src: Emoji['id'] | Emoji,
+	): Promise<Packed<'EmojiDetailed'>> {
+		const emoji = typeof src === 'object' ? src : await this.emojisRepository.findOneByOrFail({ id: src });
+
+		return {
+			id: emoji.id,
+			aliases: emoji.aliases,
+			name: emoji.name,
+			category: emoji.category,
+			host: emoji.host,
+			// || emoji.originalUrl してるのは後方互換性のため(publicUrlはstringなので??はだめ)
+			url: emoji.publicUrl || emoji.originalUrl,
+		};
+	}
+
+	@bindThis
+	public packDetailedMany(
+		emojis: any[],
+	) {
+		return Promise.all(emojis.map(x => this.packDetailed(x)));
 	}
 }
 
diff --git a/packages/backend/src/misc/schema.ts b/packages/backend/src/misc/schema.ts
index 6bd714b0f9..7fc4a3e654 100644
--- a/packages/backend/src/misc/schema.ts
+++ b/packages/backend/src/misc/schema.ts
@@ -26,7 +26,7 @@ import { packedClipSchema } from '@/models/schema/clip.js';
 import { packedFederationInstanceSchema } from '@/models/schema/federation-instance.js';
 import { packedQueueCountSchema } from '@/models/schema/queue.js';
 import { packedGalleryPostSchema } from '@/models/schema/gallery-post.js';
-import { packedEmojiSchema } from '@/models/schema/emoji.js';
+import { packedEmojiDetailedSchema, packedEmojiSimpleSchema } from '@/models/schema/emoji.js';
 import { packedFlashSchema } from '@/models/schema/flash.js';
 
 export const refs = {
@@ -57,7 +57,8 @@ export const refs = {
 	Clip: packedClipSchema,
 	FederationInstance: packedFederationInstanceSchema,
 	GalleryPost: packedGalleryPostSchema,
-	Emoji: packedEmojiSchema,
+	EmojiSimple: packedEmojiSimpleSchema,
+	EmojiDetailed: packedEmojiDetailedSchema,
 	Flash: packedFlashSchema,
 };
 
diff --git a/packages/backend/src/models/schema/emoji.ts b/packages/backend/src/models/schema/emoji.ts
index 143f25373c..c00c3dac1d 100644
--- a/packages/backend/src/models/schema/emoji.ts
+++ b/packages/backend/src/models/schema/emoji.ts
@@ -1,11 +1,37 @@
-export const packedEmojiSchema = {
+export const packedEmojiSimpleSchema = {
+	type: 'object',
+	properties: {
+		aliases: {
+			type: 'array',
+			optional: false, nullable: false,
+			items: {
+				type: 'string',
+				optional: false, nullable: false,
+				format: 'id',
+			},
+		},
+		name: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+		category: {
+			type: 'string',
+			optional: false, nullable: true,
+		},
+		url: {
+			type: 'string',
+			optional: false, nullable: false,
+		},
+	},
+} as const;
+
+export const packedEmojiDetailedSchema = {
 	type: 'object',
 	properties: {
 		id: {
 			type: 'string',
-			optional: true, nullable: false,
+			optional: false, nullable: false,
 			format: 'id',
-			example: 'xxxxxxxxxx',
 		},
 		aliases: {
 			type: 'array',
@@ -26,12 +52,12 @@ export const packedEmojiSchema = {
 		},
 		host: {
 			type: 'string',
-			optional: true, nullable: true,
+			optional: false, nullable: true,
 			description: 'The local host is represented with `null`.',
 		},
 		url: {
 			type: 'string',
-			optional: true, nullable: false,
+			optional: false, nullable: false,
 		},
 	},
 } as const;
diff --git a/packages/backend/src/server/api/ApiCallService.ts b/packages/backend/src/server/api/ApiCallService.ts
index 8aa570213d..cc27b36966 100644
--- a/packages/backend/src/server/api/ApiCallService.ts
+++ b/packages/backend/src/server/api/ApiCallService.ts
@@ -219,8 +219,8 @@ export class ApiCallService implements OnApplicationShutdown {
 
 			const limit = Object.assign({}, ep.meta.limit);
 
-			if (!limit.key) {
-				limit.key = ep.name;
+			if (limit.key == null) {
+				(limit as any).key = ep.name;
 			}
 
 			// TODO: 毎リクエスト計算するのもあれだしキャッシュしたい
diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts
index 23aa7fab0f..d05005b078 100644
--- a/packages/backend/src/server/api/endpoints.ts
+++ b/packages/backend/src/server/api/endpoints.ts
@@ -1,4 +1,5 @@
 import type { Schema } from '@/misc/schema.js';
+import { RolePolicies } from '@/core/RoleService.js';
 
 import * as ep___admin_meta from './endpoints/admin/meta.js';
 import * as ep___admin_abuseUserReports from './endpoints/admin/abuse-user-reports.js';
@@ -659,7 +660,7 @@ export interface IEndpointMeta {
 	 */
 	readonly requireAdmin?: boolean;
 
-	readonly requireRolePolicy?: string;
+	readonly requireRolePolicy?: keyof RolePolicies;
 
 	/**
 	 * エンドポイントのリミテーションに関するやつ
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
index c683cd24c1..0cc60e9191 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/add-aliases-bulk.ts
@@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 
 			this.globalEventService.publishBroadcastStream('emojiUpdated', {
-				emojis: await this.emojiEntityService.packMany(ps.ids),
+				emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
index b4fc7fd6f5..8885a40fd9 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/copy.ts
@@ -92,7 +92,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 
 			this.globalEventService.publishBroadcastStream('emojiAdded', {
-				emoji: await this.emojiEntityService.pack(copied.id),
+				emoji: await this.emojiEntityService.packDetailed(copied.id),
 			});
 
 			return {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
index 0c337237d3..f298baaedf 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete-bulk.ts
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			}
 
 			this.globalEventService.publishBroadcastStream('emojiDeleted', {
-				emojis: await this.emojiEntityService.packMany(emojis),
+				emojis: await this.emojiEntityService.packDetailedMany(emojis),
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
index c51e7fd1a8..a5fbe3f4ea 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/delete.ts
@@ -4,9 +4,9 @@ import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { EmojisRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
 import { ModerationLogService } from '@/core/ModerationLogService.js';
-import { ApiError } from '../../../error.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { ApiError } from '../../../error.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -57,7 +57,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 
 			this.globalEventService.publishBroadcastStream('emojiDeleted', {
-				emojis: [await this.emojiEntityService.pack(emoji)],
+				emojis: [await this.emojiEntityService.packDetailed(emoji)],
 			});
 
 			this.moderationLogService.insertModerationLog(me, 'deleteEmoji', {
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
index 8e0ea2e117..df3c28deff 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list-remote.ts
@@ -101,7 +101,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				.take(ps.limit)
 				.getMany();
 
-			return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false });
+			return this.emojiEntityService.packDetailedMany(emojis);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
index 1b1931f8e6..814668294f 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/list.ts
@@ -98,7 +98,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				emojis = await q.take(ps.limit).getMany();
 			}
 
-			return this.emojiEntityService.packMany(emojis, { omitHost: false, omitId: false, withUrl: false });
+			return this.emojiEntityService.packDetailedMany(emojis);
 		});
 	}
 }
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
index 065965f64a..66547024f7 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/remove-aliases-bulk.ts
@@ -56,7 +56,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 		
 			this.globalEventService.publishBroadcastStream('emojiUpdated', {
-				emojis: await this.emojiEntityService.packMany(ps.ids),
+				emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
index 51c0f329ac..c8992eeb04 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-aliases-bulk.ts
@@ -52,7 +52,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 
 			this.globalEventService.publishBroadcastStream('emojiUpdated', {
-				emojis: await this.emojiEntityService.packMany(ps.ids),
+				emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
index 3329cab7b9..8a538c1003 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/set-category-bulk.ts
@@ -54,7 +54,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 
 			this.globalEventService.publishBroadcastStream('emojiUpdated', {
-				emojis: await this.emojiEntityService.packMany(ps.ids),
+				emojis: await this.emojiEntityService.packDetailedMany(ps.ids),
 			});
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
index af610ddf9c..809bf77d6b 100644
--- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
+++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts
@@ -3,9 +3,9 @@ import { DataSource } from 'typeorm';
 import { Endpoint } from '@/server/api/endpoint-base.js';
 import type { EmojisRepository } from '@/models/index.js';
 import { DI } from '@/di-symbols.js';
-import { ApiError } from '../../../error.js';
 import { EmojiEntityService } from '@/core/entities/EmojiEntityService.js';
 import { GlobalEventService } from '@/core/GlobalEventService.js';
+import { ApiError } from '../../../error.js';
 
 export const meta = {
 	tags: ['admin'],
@@ -68,7 +68,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 
 			await this.db.queryResultCache!.remove(['meta_emojis']);
 
-			const updated = await this.emojiEntityService.pack(emoji.id);
+			const updated = await this.emojiEntityService.packDetailed(emoji.id);
 
 			if (emoji.name === ps.name) {
 				this.globalEventService.publishBroadcastStream('emojiUpdated', {
@@ -76,7 +76,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				});
 			} else {
 				this.globalEventService.publishBroadcastStream('emojiDeleted', {
-					emojis: [await this.emojiEntityService.pack(emoji)],
+					emojis: [await this.emojiEntityService.packDetailed(emoji)],
 				});
 
 				this.globalEventService.publishBroadcastStream('emojiAdded', {
diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts
index 2b19104ea7..9eef1b29c5 100644
--- a/packages/backend/src/server/api/endpoints/admin/meta.ts
+++ b/packages/backend/src/server/api/endpoints/admin/meta.ts
@@ -54,86 +54,22 @@ export const meta = {
 			},
 			mascotImageUrl: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: false, nullable: true,
 				default: '/assets/ai.png',
 			},
 			bannerUrl: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: false, nullable: true,
 			},
 			errorImageUrl: {
 				type: 'string',
-				optional: false, nullable: false,
+				optional: false, nullable: true,
 				default: 'https://xn--931a.moe/aiart/yubitun.png',
 			},
 			iconUrl: {
 				type: 'string',
 				optional: false, nullable: true,
 			},
-			maxNoteTextLength: {
-				type: 'number',
-				optional: false, nullable: false,
-			},
-			emojis: {
-				type: 'array',
-				optional: false, nullable: false,
-				items: {
-					type: 'object',
-					optional: false, nullable: false,
-					properties: {
-						id: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'id',
-						},
-						aliases: {
-							type: 'array',
-							optional: false, nullable: false,
-							items: {
-								type: 'string',
-								optional: false, nullable: false,
-							},
-						},
-						category: {
-							type: 'string',
-							optional: false, nullable: true,
-						},
-						host: {
-							type: 'string',
-							optional: false, nullable: true,
-						},
-						url: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'url',
-						},
-					},
-				},
-			},
-			ads: {
-				type: 'array',
-				optional: false, nullable: false,
-				items: {
-					type: 'object',
-					optional: false, nullable: false,
-					properties: {
-						place: {
-							type: 'string',
-							optional: false, nullable: false,
-						},
-						url: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'url',
-						},
-						imageUrl: {
-							type: 'string',
-							optional: false, nullable: false,
-							format: 'url',
-						},
-					},
-				},
-			},
 			enableEmail: {
 				type: 'boolean',
 				optional: false, nullable: false,
@@ -146,10 +82,6 @@ export const meta = {
 				type: 'boolean',
 				optional: false, nullable: false,
 			},
-			proxyAccountName: {
-				type: 'string',
-				optional: false, nullable: true,
-			},
 			userStarForReactionFallback: {
 				type: 'boolean',
 				optional: true, nullable: false,
@@ -228,7 +160,7 @@ export const meta = {
 				optional: true, nullable: true,
 			},
 			smtpPort: {
-				type: 'string',
+				type: 'number',
 				optional: true, nullable: true,
 			},
 			smtpUser: {
@@ -299,6 +231,10 @@ export const meta = {
 				type: 'boolean',
 				optional: true, nullable: false,
 			},
+			policies: {
+				type: 'object',
+				optional: false, nullable: false,
+			},
 		},
 	},
 } as const;
@@ -349,7 +285,6 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				iconUrl: instance.iconUrl,
 				backgroundImageUrl: instance.backgroundImageUrl,
 				logoImageUrl: instance.logoImageUrl,
-				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
 				defaultLightTheme: instance.defaultLightTheme,
 				defaultDarkTheme: instance.defaultDarkTheme,
 				enableEmail: instance.enableEmail,
diff --git a/packages/backend/src/server/api/endpoints/emojis.ts b/packages/backend/src/server/api/endpoints/emojis.ts
index a909dddfd6..325b758358 100644
--- a/packages/backend/src/server/api/endpoints/emojis.ts
+++ b/packages/backend/src/server/api/endpoints/emojis.ts
@@ -82,11 +82,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 			});
 
 			return {
-				emojis: await this.emojiEntityService.packMany(emojis, {
-					omitId: true,
-					omitHost: true,
-					withUrl: true,
-				}),
+				emojis: await this.emojiEntityService.packSimpleMany(emojis),
 			};
 		});
 	}
diff --git a/packages/backend/src/server/api/endpoints/meta.ts b/packages/backend/src/server/api/endpoints/meta.ts
index f7463b8ccf..cdb314a873 100644
--- a/packages/backend/src/server/api/endpoints/meta.ts
+++ b/packages/backend/src/server/api/endpoints/meta.ts
@@ -295,7 +295,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> {
 				iconUrl: instance.iconUrl,
 				backgroundImageUrl: instance.backgroundImageUrl,
 				logoImageUrl: instance.logoImageUrl,
-				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH, // 後方互換性のため
+				maxNoteTextLength: MAX_NOTE_TEXT_LENGTH,
 				defaultLightTheme: instance.defaultLightTheme,
 				defaultDarkTheme: instance.defaultDarkTheme,
 				ads: ads.map(ad => ({
diff --git a/packages/backend/src/server/api/stream/types.ts b/packages/backend/src/server/api/stream/types.ts
index bf6ece2864..9287952cb6 100644
--- a/packages/backend/src/server/api/stream/types.ts
+++ b/packages/backend/src/server/api/stream/types.ts
@@ -42,10 +42,10 @@ export interface InternalStreamTypes {
 
 export interface BroadcastTypes {
 	emojiAdded: {
-		emoji: Packed<'Emoji'>;
+		emoji: Packed<'EmojiDetailed'>;
 	};
 	emojiUpdated: {
-		emojis: Packed<'Emoji'>[];
+		emojis: Packed<'EmojiDetailed'>[];
 	};
 	emojiDeleted: {
 		emojis: {
diff --git a/packages/backend/test/misc/mock-resolver.ts b/packages/backend/test/misc/mock-resolver.ts
index 9efed267ea..6b31e68616 100644
--- a/packages/backend/test/misc/mock-resolver.ts
+++ b/packages/backend/test/misc/mock-resolver.ts
@@ -1,5 +1,16 @@
-import Resolver from '../../src/activitypub/resolver.js';
-import { IObject } from '../../src/activitypub/type.js';
+import type { Config } from '@/config.js';
+import type { ApDbResolverService } from '@/core/activitypub/ApDbResolverService.js';
+import type { ApRendererService } from '@/core/activitypub/ApRendererService.js';
+import type { ApRequestService } from '@/core/activitypub/ApRequestService.js';
+import { Resolver } from '@/core/activitypub/ApResolverService.js';
+import type { IObject } from '@/core/activitypub/type.js';
+import type { HttpRequestService } from '@/core/HttpRequestService.js';
+import type { InstanceActorService } from '@/core/InstanceActorService.js';
+import type { LoggerService } from '@/core/LoggerService.js';
+import type { MetaService } from '@/core/MetaService.js';
+import type { UtilityService } from '@/core/UtilityService.js';
+import { bindThis } from '@/decorators.js';
+import type { NoteReactionsRepository, NotesRepository, PollsRepository, UsersRepository } from '@/models/index.js';
 
 type MockResponse = {
 	type: string;
@@ -8,6 +19,25 @@ type MockResponse = {
 
 export class MockResolver extends Resolver {
 	private _rs = new Map<string, MockResponse>();
+
+	constructor(loggerService: LoggerService) {
+		super(
+			{} as Config,
+			{} as UsersRepository,
+			{} as NotesRepository,
+			{} as PollsRepository,
+			{} as NoteReactionsRepository,
+			{} as UtilityService,
+			{} as InstanceActorService,
+			{} as MetaService,
+			{} as ApRequestService,
+			{} as HttpRequestService,
+			{} as ApRendererService,
+			{} as ApDbResolverService,
+			loggerService,
+		);
+	}
+
 	public async _register(uri: string, content: string | Record<string, any>, type = 'application/activity+json') {
 		this._rs.set(uri, {
 			type,
diff --git a/packages/backend/test/tests/activitypub.ts b/packages/backend/test/unit/activitypub.ts
similarity index 56%
rename from packages/backend/test/tests/activitypub.ts
rename to packages/backend/test/unit/activitypub.ts
index 19fb5d90d7..3d0032507e 100644
--- a/packages/backend/test/tests/activitypub.ts
+++ b/packages/backend/test/unit/activitypub.ts
@@ -2,8 +2,39 @@ process.env.NODE_ENV = 'test';
 
 import * as assert from 'assert';
 import rndstr from 'rndstr';
+import { Test } from '@nestjs/testing';
+import { jest } from '@jest/globals';
+
+import { ApNoteService } from '@/core/activitypub/models/ApNoteService.js';
+import { ApPersonService } from '@/core/activitypub/models/ApPersonService.js';
+import { GlobalModule } from '@/GlobalModule.js';
+import { CoreModule } from '@/core/CoreModule.js';
+import { FederatedInstanceService } from '@/core/FederatedInstanceService.js';
+import { LoggerService } from '@/core/LoggerService.js';
+import { MockResolver } from '../misc/mock-resolver.js';
 
 describe('ActivityPub', () => {
+	let noteService: ApNoteService;
+	let personService: ApPersonService;
+	let resolver: MockResolver;
+
+	beforeEach(async () => {
+		const app = await Test.createTestingModule({
+			imports: [GlobalModule, CoreModule],
+		}).compile();
+
+		await app.init();
+		app.enableShutdownHooks();
+
+		noteService = app.get<ApNoteService>(ApNoteService);
+		personService = app.get<ApPersonService>(ApPersonService);
+		resolver = new MockResolver(await app.resolve<LoggerService>(LoggerService));
+
+		// Prevent ApPersonService from fetching instance, as it causes Jest import-after-test error
+		const federatedInstanceService = app.get<FederatedInstanceService>(FederatedInstanceService);
+		jest.spyOn(federatedInstanceService, 'fetch').mockImplementation(() => new Promise(() => {}));
+	});
+
 	describe('Parse minimum object', () => {
 		const host = 'https://host1.test';
 		const preferredUsername = `${rndstr('A-Z', 4)}${rndstr('a-z', 4)}`;
@@ -28,13 +59,9 @@ describe('ActivityPub', () => {
 		};
 
 		test('Minimum Actor', async () => {
-			const { MockResolver } = await import('../misc/mock-resolver.js');
-			const { createPerson } = await import('../../src/activitypub/models/person.js');
-
-			const resolver = new MockResolver();
 			resolver._register(actor.id, actor);
 
-			const user = await createPerson(actor.id, resolver);
+			const user = await personService.createPerson(actor.id, resolver);
 
 			assert.deepStrictEqual(user.uri, actor.id);
 			assert.deepStrictEqual(user.username, actor.preferredUsername);
@@ -42,14 +69,10 @@ describe('ActivityPub', () => {
 		});
 
 		test('Minimum Note', async () => {
-			const { MockResolver } = await import('../misc/mock-resolver.js');
-			const { createNote } = await import('../../src/activitypub/models/note.js');
-
-			const resolver = new MockResolver();
 			resolver._register(actor.id, actor);
 			resolver._register(post.id, post);
 
-			const note = await createNote(post.id, resolver, true);
+			const note = await noteService.createNote(post.id, resolver, true);
 
 			assert.deepStrictEqual(note?.uri, post.id);
 			assert.deepStrictEqual(note.visibility, 'public');
@@ -75,13 +98,9 @@ describe('ActivityPub', () => {
 		};
 
 		test('Actor', async () => {
-			const { MockResolver } = await import('../misc/mock-resolver.js');
-			const { createPerson } = await import('../../src/activitypub/models/person.js');
-
-			const resolver = new MockResolver();
 			resolver._register(actor.id, actor);
 
-			const user = await createPerson(actor.id, resolver);
+			const user = await personService.createPerson(actor.id, resolver);
 
 			assert.deepStrictEqual(user.name, actor.name.substr(0, 128));
 		});
diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts
index c121b30bef..da92b37d19 100644
--- a/packages/sw/src/scripts/create-notification.ts
+++ b/packages/sw/src/scripts/create-notification.ts
@@ -10,6 +10,12 @@ import { getAccountFromId } from '@/scripts/get-account-from-id';
 import { char2fileName } from '@/scripts/twemoji-base';
 import * as url from '@/scripts/url';
 
+const closeNotificationsByTags = async (tags: string[]) => {
+	for (const n of (await Promise.all(tags.map(tag => globalThis.registration.getNotifications({ tag })))).flat()) {
+		n.close();
+	}
+};
+
 const iconUrl = (name: badgeNames) => `/static-assets/tabler-badges/${name}.png`;
 /* How to add a new badge:
  * 1. Find the icon and download png from https://tabler-icons.io/
@@ -23,7 +29,7 @@ export async function createNotification<K extends keyof pushNotificationDataMap
 	const n = await composeNotification(data);
 
 	if (n) {
-		return self.registration.showNotification(...n);
+		return globalThis.registration.showNotification(...n);
 	} else {
 		console.error('Could not compose notification', data);
 		return createEmptyNotification();
@@ -161,6 +167,7 @@ async function composeNotification(data: pushNotificationDataMap[keyof pushNotif
 						badge = iconUrl('plus');
 					}
 
+					const tag = `reaction:${data.body.note.id}`;
 					return [`${reaction} ${getUserName(data.body.user)}`, {
 						body: data.body.note.text ?? '',
 						icon: data.body.user.avatarUrl,
@@ -239,7 +246,7 @@ export async function createEmptyNotification() {
 		const i18n = await swLang.i18n as I18n<any>;
 		const { t } = i18n;
 
-		await self.registration.showNotification(
+		await globalThis.registration.showNotification(
 			t('_notification.emptyPushNotificationMessage'),
 			{
 				silent: true,
@@ -248,16 +255,11 @@ export async function createEmptyNotification() {
 			},
 		);
 
-		res();
-
 		setTimeout(async () => {
-			for (const n of
-				[
-					...(await self.registration.getNotifications({ tag: 'user_visible_auto_notification' })),
-					...(await self.registration.getNotifications({ tag: 'read_notification' })),
-				]
-			) {
-				n.close();
+			try {
+				await closeNotificationsByTags(['user_visible_auto_notification', 'read_notification']);
+			} finally {
+				res();
 			}
 		}, 1000);
 	});
diff --git a/packages/sw/src/scripts/operations.ts b/packages/sw/src/scripts/operations.ts
index 4d693223b2..8936a7763a 100644
--- a/packages/sw/src/scripts/operations.ts
+++ b/packages/sw/src/scripts/operations.ts
@@ -51,11 +51,11 @@ export async function openClient(order: swMessageOrderType, url: string, loginId
 		return client;
 	}
 
-	return self.clients.openWindow(getUrlWithLoginId(url, loginId));
+	return globalThis.clients.openWindow(getUrlWithLoginId(url, loginId));
 }
 
 export async function findClient() {
-	const clients = await self.clients.matchAll({
+	const clients = await globalThis.clients.matchAll({
 		type: 'window',
 	});
 	for (const c of clients) {
diff --git a/packages/sw/src/sw.ts b/packages/sw/src/sw.ts
index c392d03232..6f4c487354 100644
--- a/packages/sw/src/sw.ts
+++ b/packages/sw/src/sw.ts
@@ -6,7 +6,7 @@ import * as swos from '@/scripts/operations';
 import { acct as getAcct } from '@/filters/user';
 
 globalThis.addEventListener('install', ev => {
-	//ev.waitUntil(self.skipWaiting());
+	//ev.waitUntil(globalThis.skipWaiting());
 });
 
 globalThis.addEventListener('activate', ev => {
@@ -17,7 +17,7 @@ globalThis.addEventListener('activate', ev => {
 					.filter((v) => v !== swLang.cacheName)
 					.map(name => caches.delete(name)),
 			))
-			.then(() => self.clients.claim()),
+			.then(() => globalThis.clients.claim()),
 	);
 });
 
@@ -40,7 +40,7 @@ globalThis.addEventListener('fetch', ev => {
 
 globalThis.addEventListener('push', ev => {
 	// クライアント取得
-	ev.waitUntil(self.clients.matchAll({
+	ev.waitUntil(globalThis.clients.matchAll({
 		includeUncontrolled: true,
 		type: 'window',
 	}).then(async (clients: readonly WindowClient[]) => {
@@ -53,29 +53,26 @@ globalThis.addEventListener('push', ev => {
 				// 1日以上経過している場合は無視
 				if ((new Date()).getTime() - data.dateTime > 1000 * 60 * 60 * 24) break;
 
-				// クライアントがあったらストリームに接続しているということなので通知しない
-				if (clients.length !== 0) break;
-
 				return createNotification(data);
 			case 'readAllNotifications':
-				for (const n of await self.registration.getNotifications()) {
+				for (const n of await globalThis.registration.getNotifications()) {
 					if (n?.data?.type === 'notification') n.close();
 				}
 				break;
 			case 'readAllAntennas':
-				for (const n of await self.registration.getNotifications()) {
+				for (const n of await globalThis.registration.getNotifications()) {
 					if (n?.data?.type === 'unreadAntennaNote') n.close();
 				}
 				break;
 			case 'readNotifications':
-				for (const n of await self.registration.getNotifications()) {
+				for (const n of await globalThis.registration.getNotifications()) {
 					if (data.body.notificationIds.includes(n.data.body.id)) {
 						n.close();
 					}
 				}
 				break;
 			case 'readAntenna':
-				for (const n of await self.registration.getNotifications()) {
+				for (const n of await globalThis.registration.getNotifications()) {
 					if (n?.data?.type === 'unreadAntennaNote' && data.body.antennaId === n.data.body.antenna.id) {
 						n.close();
 					}
@@ -83,7 +80,8 @@ globalThis.addEventListener('push', ev => {
 				break;
 		}
 
-		return createEmptyNotification();
+		await createEmptyNotification();
+		return;
 	}));
 });